/*
 * Copyright 2009 Brook Smith brook.smith@paradise.net.nz.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.javagenerate.generator.script;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;

import org.javagenerate.generator.script.gen.GwtJdoGenSet;

public class ScriptReader {

	public PatternGenerator getPatternGenerator() {
		return patternGenerator;
	}

	private static final String CLASSES_IMPORTS = "classes.imports";
	private static final String GENSET_IMPORTS = "genset.imports";
	private static final String CLASSES_METHODS_METHOD = "classes.methods.method";
	private static final String CLASSES_CONSTRUCTORS = "classes.constructors";
	private static final String GENSET_CONSTRUCTORS = "genset.constructors";
	private static final String CLASSES_METHODS = "classes.methods";
	private static final String CLASSES_CODE = "classes.code";
	private static final String GENSET_METHODS = "genset.methods";
	private static final String CLASSES_PROPERTIES = "classes.properties";
	private static final String GENSET_PROPERTIES = "genset.properties";
	
	private static final String PROPERTIES = "properties";
	private static final String GENSET2 = "genset";
	private static final String METHODS = "methods";
	private static final String CLASSES = "classes";
	private static final String CONSTRUCTORS = "constructors";
	private static final String CODE = "code";
	private static final String IMPORTS = "imports";
	private static final String TEMPLATES = "templates";

	private static Map<String, Integer> MIN_TOKENS = new HashMap<String, Integer>();
	private static Set<String> SNIPIT_HANDLER = new HashSet<String>();
	private Properties properties = null;

	private boolean codeSnipit = false;

	static {
		MIN_TOKENS.put("parser", 0);
		MIN_TOKENS.put(GENSET2, 0);
		MIN_TOKENS.put(GENSET_PROPERTIES, 2);
		MIN_TOKENS.put(CLASSES_PROPERTIES, 2);
		MIN_TOKENS.put(CLASSES, 2);
		MIN_TOKENS.put(TEMPLATES, 2);
		MIN_TOKENS.put(GENSET_METHODS, 2);
		MIN_TOKENS.put(CLASSES_METHODS, 2);
		MIN_TOKENS.put(CLASSES_METHODS_METHOD, 1);
		MIN_TOKENS.put(GENSET_CONSTRUCTORS, 2);
		MIN_TOKENS.put(CLASSES_CONSTRUCTORS, 2);
		MIN_TOKENS.put(CLASSES_IMPORTS, 1);
		MIN_TOKENS.put(GENSET_IMPORTS, 1);
		MIN_TOKENS.put(CLASSES_CODE, 1);

		SNIPIT_HANDLER.add(CLASSES_METHODS_METHOD);
		SNIPIT_HANDLER.add(CLASSES_CODE);
		
	}

	private StreamTokenizer streamTokenizer;
	private BasePatternGenerator patternGenerator = null;
	private Stack<String> comStack = new Stack<String>();
	private String currentHandler = null;
	private List<String> unresolvedTokens = new ArrayList<String>();
	private String outDir;

	public ScriptReader(InputStream in, Properties properties, String outDir) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		TemplateReader.parse(in, out, properties);
		this.properties = properties;
		this.outDir = outDir;
		
		streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray()))));
		comStack.push("parser");
	}

	private void addText(String text) {
		if (!text.equals("\n")) {
			text = text.trim();
		}
		if (text.length() == 0) {
			if (unresolvedTokens.size() > 0) {
				unresolvedTokens.add(" ");
			}
		} else {
			unresolvedTokens.add(text);
		}
	}

	private void handleCharactor(int charactor) {
		if ((char) charactor == '{') {
			if (codeSnipit) {
				resolveTokens();
				addText("{");
			}
			resolveTokens();
			pushHandler();
		} else if ((char) charactor == '}') {
			if (codeSnipit) {
				resolveTokens();
				addText("}");
			}
			popHandler();
			resolveTokens();
//			System.out.println("{comstack,pop:" + comStack + "}");
		} else if ((char) charactor == ';') {
			if (codeSnipit) {
				addText(";");
			}
			resolveTokens();
		} else {
			addText(String.valueOf((char) charactor));
		}
	}

	private void handleEOL() {
		if (codeSnipit) {
			addText("\n");
		}
	}

	private void handleNumber(Double number) {
		addText(number.toString());
	}

	private void handleWord(String word) {
		if (word.equals(GENSET2)) {
			setHandler(GENSET2);
		} else if (word.equals(PROPERTIES)) {
			if (comStack.peek().equals(GENSET2)) {
				setHandler(GENSET_PROPERTIES);
			} else if (comStack.peek().equals(CLASSES)) {
				setHandler(CLASSES_PROPERTIES);
			}
		} else if (word.equals(CLASSES)) {
			setHandler(CLASSES);
		} else if (word.equals(TEMPLATES)) {
			setHandler(TEMPLATES);
		} else if (word.equals(METHODS)) {
			if (comStack.peek().equals(GENSET2)) {
				setHandler(GENSET_METHODS);
			} else if (comStack.peek().equals(CLASSES)) {
				setHandler(CLASSES_METHODS);
			}
		} else if (word.equals(IMPORTS)) {
			if (comStack.peek().equals(GENSET2)) {
				setHandler(GENSET_IMPORTS);
			} else if (comStack.peek().equals(CLASSES)) {
				setHandler(CLASSES_IMPORTS);
			}
		} else if (word.equals(CONSTRUCTORS)) {
			if (comStack.peek().equals(GENSET2)) {
				setHandler(GENSET_CONSTRUCTORS);
			} else if (comStack.peek().equals(CLASSES)) {
				setHandler(CLASSES_CONSTRUCTORS);
			}
		} else if (word.equals(CODE)) {
			if (comStack.peek().equals(CLASSES)) {
				setHandler(CLASSES_CODE);
			}
		}
		addText(word);
	}

	public void parse() throws IOException {
		streamTokenizer.eolIsSignificant(true);

		streamTokenizer.ordinaryChars(0, ' ');
		streamTokenizer.ordinaryChars('0', '9');
		streamTokenizer.ordinaryChar('/');
		streamTokenizer.ordinaryChar('"');
		streamTokenizer.ordinaryChar('\'');
		streamTokenizer.ordinaryChar('\\');
		streamTokenizer.ordinaryChar('.');
		streamTokenizer.wordChars('.', '.');
		streamTokenizer.ordinaryChar('*');
		streamTokenizer.wordChars('*', '*');
		
		streamTokenizer.slashSlashComments(true);
		streamTokenizer.slashStarComments(true);

		while (streamTokenizer.nextToken() != StreamTokenizer.TT_EOF) {
			String s;
			switch (streamTokenizer.ttype) {
			case StreamTokenizer.TT_EOL:
				handleEOL();
				break;
			case StreamTokenizer.TT_NUMBER:
//				System.out.println("number " + streamTokenizer.sval);
				handleNumber(streamTokenizer.nval);
				break;
			case StreamTokenizer.TT_WORD:
				handleWord(streamTokenizer.sval);
				break;
			default: // single character in ttype
				handleCharactor(streamTokenizer.ttype);
			}
		}
		for (BasePatternGenerator generator : PatternRef.getCollection()) {
			generator.write(outDir);
		}
	}

	private void popHandler() {
		currentHandler = comStack.pop();
		if (!SNIPIT_HANDLER.contains(comStack.peek())) {
			codeSnipit = false;
		}

		System.out.println("{comstack,pop:" + comStack + "} " + unresolvedTokens);
	}

	private void pushHandler() {
		if (SNIPIT_HANDLER.contains(currentHandler)) {
			codeSnipit = true;
		}
		comStack.push(currentHandler);
		System.out.println("{comstack,push:" + comStack + "} " + unresolvedTokens);
	}
	
	private void parser() {
		// factory - it may not be genset
		if (unresolvedTokens.size() < 1) {
			return;
		}
		System.out.println("parser " + unresolvedTokens);
		String parserName = null;
		String name = null;
		for (String string : unresolvedTokens) {
			if (string == null || string.trim().length() == 0) {
				continue;
			} else if (parserName == null) {
				parserName = string;
			} else {
				name = string;
			}
		}
		if (parserName.equals("genset")) {
			patternGenerator = new GwtJdoGenSet(properties, name);
		} else {
			throw new RuntimeException("Unknown pattern " + parserName);
		}
		PatternRef.addPatternGenerator(name, patternGenerator);
	}

	private void resolveTokens() {
//		System.out.println("{resolve:" + unresolvedTokens + "}{comstack:" + comStack + "}");
		int tokens = unresolvedTokens.size();
		String handler = comStack.peek();

		int minTokens = MIN_TOKENS.get(handler);
		if (tokens < minTokens) {
			// dump the rubbish
			System.out.println("{Dunmping:" + unresolvedTokens + "}");
			unresolvedTokens.clear();
			return;
		}

		if (handler.equals(GENSET2)) {
			if (patternGenerator == null) {
//				System.out.println("new genset " + unresolvedTokens);
//				patternGenerator = new GwtJdoGenSet(properties);
			}
		} else if (handler.equals("parser")) {
			if (patternGenerator != null) {
//				System.out.println("write genset (parser)");
//				patternGenerator.write(outDir);
				patternGenerator = null;
			} else {
				parser();
			}
		} else if (handler.equals(GENSET_PROPERTIES)) {
			patternGenerator.addBeanDefinition(unresolvedTokens);
		} else if (handler.equals(CLASSES_PROPERTIES)) {
			patternGenerator.addClassProperty(unresolvedTokens);
		} else if (handler.equals(CLASSES)) {
			patternGenerator.addClassDefinition(unresolvedTokens);
		} else if (handler.equals(TEMPLATES)) {
			patternGenerator.addTemplateDefinition(unresolvedTokens);
		} else if (handler.equals(GENSET_METHODS)) {
		} else if (handler.equals(CLASSES_METHODS)) {
			patternGenerator.addClassMethod(unresolvedTokens);
			currentHandler = CLASSES_METHODS_METHOD;
		} else if (handler.equals(CLASSES_METHODS_METHOD)) {
			patternGenerator.addClassMethodContent(unresolvedTokens);
		} else if (handler.equals(GENSET_CONSTRUCTORS)) {
		} else if (handler.equals(CLASSES_CONSTRUCTORS)) {

		} else if (handler.equals(CLASSES_IMPORTS)) {
			patternGenerator.addClassImport(unresolvedTokens);
		} else if (handler.equals(GENSET_IMPORTS)) {
			patternGenerator.addImport(unresolvedTokens);
		} else if (handler.equals(CLASSES_CODE)) {
			patternGenerator.addCode(unresolvedTokens);
		}
		unresolvedTokens.clear();
	}

	private void setHandler(String handlerName) {
		currentHandler = handlerName;
		resolveTokens();
	}

}
